package cbfg.butil

import android.util.Log
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.*
import javax.xml.transform.OutputKeys
import javax.xml.transform.Source
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource

/**
 * 参考 https://github.com/ZhaoKaiQiang/KLog
 */
object KLog {
    private const val NULL_TIPS = "Log with null object"
    private const val PARAM = "Param"
    private const val NULL = "null"
    private const val JSON_INDENT = 4
    private const val STACK_TRACE_INDEX_5 = 5

    /**
     * 对于长文本要进行分段，这个是分段的长度，如果太大可能导致内容输出不全
     */
    private const val MAX_LENGTH = 1350

    private const val V = 0x1
    private const val D = 0x2
    private const val I = 0x3
    private const val W = 0x4
    private const val E = 0x5
    private const val A = 0x6
    private const val JSON = 0x7
    private const val XML = 0x8

    private var globalTag: String = "KLog"
    private var isShowLog = false

    @JvmStatic
    fun init(isShowLog: Boolean, tag: String = globalTag) {
        KLog.isShowLog = isShowLog
        this.globalTag = tag
    }

    @JvmStatic
    fun v(tagOrMsg: String?, vararg objs: Any?) {
        printLog(V, tagOrMsg, *objs)
    }

    @JvmStatic
    fun d(tagOrMsg: String?, vararg objs: Any?) {
        printLog(D, tagOrMsg, *objs)
    }

    @JvmStatic
    fun i(tagOrMsg: String?, vararg objs: Any?) {
        printLog(I, tagOrMsg, *objs)
    }

    @JvmStatic
    fun w(tagOrMsg: String?, vararg objs: Any?) {
        printLog(W, tagOrMsg, *objs)
    }

    @JvmStatic
    fun e(tagOrMsg: String?, vararg objs: Any?) {
        printLog(E, tagOrMsg, *objs)
    }

    @JvmStatic
    fun a(tagOrMsg: String?, vararg objs: Any?) {
        printLog(A, tagOrMsg, *objs)
    }

    @JvmStatic
    fun json(jsonFormat: String?) {
        printLog(JSON, null, jsonFormat)
    }

    @JvmStatic
    fun json(tag: String?, jsonFormat: String?) {
        printLog(JSON, tag, jsonFormat)
    }

    @JvmStatic
    fun xml(xml: String?) {
        printLog(XML, null, xml)
    }

    @JvmStatic
    fun xml(tag: String?, xml: String?) {
        printLog(XML, tag, xml)
    }

    @JvmStatic
    fun file(
        tag: String?,
        msg: String,
        targetDir: File,
        fileName: String? = null,
        enabled: Boolean = isShowLog
    ) {
        if (!enabled) {
            return
        }
        val tagStr = tag ?: globalTag
        val headString = getHeadString()
        val mFileName = fileName ?: "${System.currentTimeMillis()}.log"
        if (save(targetDir, mFileName, msg)) {
            val tip = "$headString Save log success,path:${targetDir.absolutePath}/$mFileName"
            Log.d(tagStr, tip)
        } else {
            Log.e(tagStr, "$headString Save log fails !")
        }
    }

    /**
     * 第三个参数的 length 为 0 则表示外部没有传入第三个参数，
     * 那么第二个参数则作为内容输出
     */
    private fun printLog(
        type: Int,
        tagOrMsg: String?,
        vararg objs: Any?
    ) {
        if (!isShowLog) {
            return
        }
        val tag: String
        val msg: String
        if (objs.isEmpty()) {
            tag = globalTag
            msg = tagOrMsg ?: NULL
        } else {
            tag = tagOrMsg ?: globalTag
            msg = getObjectsString(*objs)
        }
        when (type) {
            JSON -> printJson(tag, msg, getHeadString())
            XML -> printXml(tag, msg, getHeadString())
            else -> printDefault(type, tag, getHeadString() + msg)
        }
    }

    private fun getHeadString(): String {
        val stackTrace = Thread.currentThread().stackTrace
        val element = stackTrace[STACK_TRACE_INDEX_5]
        val lineNumber = if (element.lineNumber < 0) 0 else element.lineNumber
        return "[(${element.fileName}:$lineNumber)#${element.methodName}]"
    }

    private fun getObjectsString(vararg objs: Any?): String {
        return when {
            objs.size == 1 -> getObjectAsString(objs[0])
            objs.size > 1 -> {
                val stringBuilder = StringBuilder()
                stringBuilder.append("\n")
                for (i in objs.indices) {
                    stringBuilder.append(PARAM)
                        .append("[")
                        .append(i)
                        .append("]")
                        .append(" = ")
                        .append(getObjectAsString(objs[i]))
                        .append("\n")
                }
                stringBuilder.toString()
            }
            else -> NULL
        }
    }

    private fun getObjectAsString(obj: Any?): String {
        obj ?: return NULL
        return if (obj is String) obj else obj.toString()
    }

    private fun printDefault(type: Int, tag: String?, msg: String) {
        val countOfSub = msg.length / MAX_LENGTH
        if (countOfSub == 0) {
            //msg.length<MAX_LENGTH
            printSub(type, tag, msg)
            return
        }
        //msg.length>MAX_LENGTH
        var index = 0
        for (i in 0 until countOfSub) {
            printSub(type, tag, msg.substring(index, index + MAX_LENGTH))
            index += MAX_LENGTH
        }
        printSub(type, tag, msg.substring(index, msg.length))
    }

    private fun printSub(type: Int, tag: String?, sub: String) {
        when (type) {
            V -> Log.v(tag, sub)
            D -> Log.d(tag, sub)
            I -> Log.i(tag, sub)
            W -> Log.w(tag, sub)
            E -> Log.e(tag, sub)
            A -> Log.wtf(tag, sub)
        }
    }

    private fun save(
        dic: File,
        fileName: String,
        msg: String?
    ): Boolean {
        return try {
            FileOutputStream(File(dic, fileName)).use { fos ->
                OutputStreamWriter(fos, "UTF-8").use { osw ->
                    osw.write(msg)
                    osw.flush()
                }
            }
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    }

    private fun printJson(
        tag: String?,
        msg: String?,
        headString: String?
    ) {
        printLine(tag, true)
        Log.d(tag, "║ $headString")
        if (msg.isNullOrBlank()) {
            Log.d(tag, "║ $NULL_TIPS")
        } else {
            msg.trimStart()
            val message: String = try {
                when {
                    msg.startsWith("{") -> JSONObject(msg).toString(JSON_INDENT)
                    msg.startsWith("[") -> JSONArray(msg).toString(JSON_INDENT)
                    else -> msg
                }
            } catch (e: JSONException) {
                msg
            }
            message.lineSequence().forEach { Log.d(tag, "║ $it") }
        }
        printLine(tag, false)
    }

    private fun printXml(
        tag: String?,
        xml: String?,
        headString: String?
    ) {
        printLine(tag, true)
        Log.d(tag, "║ $headString")
        if (xml.isNullOrBlank()) {
            Log.d(tag, "║ $NULL_TIPS")
        } else {
            formatXML(xml).lineSequence().forEach {
                val isEmpty = it.isBlank() || it == "\n" || it == "\t"
                if (!isEmpty) {
                    Log.d(tag, "║ $it")
                }
            }
        }
        printLine(tag, false)
    }

    private fun formatXML(inputXML: String): String {
        return try {
            val xmlInput: Source = StreamSource(StringReader(inputXML))
            val xmlOutput = StreamResult(StringWriter())
            val transformer = TransformerFactory.newInstance().newTransformer()
            transformer.setOutputProperty(OutputKeys.INDENT, "yes")
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
            transformer.transform(xmlInput, xmlOutput)
            xmlOutput.writer.toString().replaceFirst(">".toRegex(), ">\n")
        } catch (e: Exception) {
            inputXML
        }
    }

    private fun printLine(tag: String?, isTop: Boolean) {
        if (isTop) {
            Log.d(tag, "╔════════════════════════════════════════════════════════════════════════")
        } else {
            Log.d(tag, "╚════════════════════════════════════════════════════════════════════════")
        }
    }
}